package openglsuperbible.Chapter5;
import de.matthiasmann.twl.utils.PNGDecoder;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import openglsuperbible.glutils.*;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.*;
import static org.lwjgl.opengl.GL30.*;
/**
 *
 * @author Matthew
 */
public class Tunnel {
     public static final int DISPLAY_HEIGHT = 600;
        public static final int DISPLAY_WIDTH = 800;

        public long lastFrame;       
        private GLShader TextureReplaceShader;

        
        private MatrixStack modelViewMatrix;  // Modelview Matrix
        private MatrixStack projectionMatrix; // Projection Matrix
        private GLFrustrum frustrum;    // View Frustum
        private GeometryTransform transformPipeline; // Geometry Transform Pipeline
        private GLFrame cameraFrame;
        GLFrame mObjectFrame; 
        
        GLBatch   floorBatch;
        GLBatch   ceilingBatch;
        GLBatch   leftWallBatch;
        GLBatch   rightWallBatch; 
        
        int TEXTURE_BRICK  = 0;
        int TEXTURE_FLOOR  = 1;
        int TEXTURE_CEILING = 2;
        int[] textures;
        private float   viewZ = -65.0f;
        

        public void initGL()
        {
            // Black background
            glClearColor(0.0f, 0.0f, 0.0f,1.0f);
            TextureReplaceShader = GLShaderFactory.getTextureReplaceShader();
            glEnable(GL_DEPTH_TEST);
	    glEnable(GL_TEXTURE_2D); 
            frustrum = new GLFrustrum();               
            modelViewMatrix = new MatrixStack();
            cameraFrame = new GLFrame();           
            
           
            textures = new int[3];
            textures[TEXTURE_BRICK] = loadTexture("brick",GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);
            textures[TEXTURE_FLOOR] =  loadTexture("floor",GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);
            textures[TEXTURE_CEILING] =  loadTexture("ceiling",GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);
 
            
            GLBatchFactory batch = new GLBatchFactory();
            // Build Geometry
            float z;
            float w = 1.0f;
            
            for(z = 60.0f; z >= 0.0f; z -=10.0f)
            {
                    
                batch.addVertex(new float[]{-10.0f, -10.0f, z,w},           new float[]{0.0f, 1.0f,0.0f}, new float[]{0.0f, 0.0f}) ; 
                batch.addVertex(new float[]{10.0f, -10.0f, z,w},            new float[]{0.0f, 1.0f,0.0f}, new float[]{1.0f, 0.0f}) ; 
                batch.addVertex(new float[]{-10.0f, -10.0f, z - 10.0f,w},   new float[]{0.0f, 1.0f,0.0f}, new float[]{0.0f, 1.0f}) ; 
                batch.addVertex(new float[]{10.0f, -10.0f, z - 10.0f,w},    new float[]{0.0f, 1.0f,0.0f}, new float[]{1.0f, 1.0f}) ;
 
            }
           floorBatch = batch.buildBatch(GL_TRIANGLE_STRIP);
           batch.clear();     
           
            
            for(z = 60.0f; z >= 0.0f; z -=10.0f)
            {
                    
                batch.addVertex(new float[]{-10.0f, 10.0f, z - 10.0f,w},    new float[]{0.0f, -1.0f,0.0f}, new float[]{0.0f, 1.0f}) ; 
                batch.addVertex(new float[]{10.0f, 10.0f, z - 10.0f,w},     new float[]{0.0f, -1.0f,0.0f}, new float[]{1.0f, 1.0f}) ; 
                batch.addVertex(new float[]{-10.0f, 10.0f, z,w},            new float[]{0.0f, -1.0f,0.0f}, new float[]{0.0f, 0.0f}) ; 
                batch.addVertex(new float[]{10.0f, 10.0f, z,w},             new float[]{0.0f, -1.0f,0.0f}, new float[]{1.0f, 0.0f}) ;
 
            }
           ceilingBatch =  batch.buildBatch(GL_TRIANGLE_STRIP);
           batch.clear();
           
            for(z = 60.0f; z >= 0.0f; z -=10.0f)
            {
                    
                batch.addVertex(new float[]{-10.0f, -10.0f, z,w},            new float[]{1.0f, 0.0f,0.0f}, new float[]{0.0f, 0.0f}) ; 
                batch.addVertex(new float[]{-10.0f, 10.0f, z,w},             new float[]{1.0f, 0.0f,0.0f}, new float[]{0.0f, 1.0f}) ; 
                batch.addVertex(new float[]{-10.0f, -10.0f, z - 10.0f,w},    new float[]{1.0f, 0.0f,0.0f}, new float[]{1.0f, 0.0f}) ; 
                batch.addVertex(new float[]{-10.0f, 10.0f, z - 10.0f,w},     new float[]{1.0f, 0.0f,0.0f}, new float[]{1.0f, 1.0f}) ;
 
            }
           leftWallBatch = batch.buildBatch(GL_TRIANGLE_STRIP);
           batch.clear(); 
           
           
            for(z = 60.0f; z >= 0.0f; z -=10.0f)
            {
                    
                batch.addVertex(new float[]{10.0f, -10.0f, z,w},            new float[]{-1.0f, 0.0f,0.0f}, new float[]{0.0f, 0.0f}) ; 
                batch.addVertex(new float[]{10.0f, 10.0f, z,w},             new float[]{-1.0f, 0.0f,0.0f}, new float[]{0.0f, 1.0f}) ; 
                batch.addVertex(new float[]{10.0f, -10.0f, z - 10.0f,w},    new float[]{-1.0f, 0.0f,0.0f}, new float[]{1.0f, 0.0f}) ; 
                batch.addVertex(new float[]{10.0f, 10.0f, z - 10.0f,w},     new float[]{-1.0f, 0.0f,0.0f}, new float[]{1.0f, 1.0f}) ;
 
            }
            rightWallBatch = batch.buildBatch(GL_TRIANGLE_STRIP);

        }

void changeTexParameter(int value)
	{
    int iLoop;
    
    for(iLoop = 0; iLoop < 3; iLoop++)
        {
        glBindTexture(GL_TEXTURE_2D, textures[iLoop]);
        
        switch(value)
            {
            case 0:
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                break;
                
            case 1:
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                break;
                
            case 2:
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
                break;
            
            case 3:
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
                break;
            
            case 4:
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
                break;
                
            case 5:
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
                break;                
            }
        }
   
	}
  
///////////////////////////////////////////////////
// Respond to arrow keys, move the viewpoint back
// and forth
        int value = 0;
        void pollInput()
	{
            int mouse = 0;
	    int last = Mouse.getDWheel();
            mouse = mouse - last;
	   if (mouse >0){
                viewZ += 1;
           }
           if (mouse <0){
                viewZ -= 1;
           }
            
            	if (Keyboard.isKeyDown(Keyboard.KEY_UP)) {
                     viewZ += 0.5f;
                     System.out.println("mouse = " + mouse);
                }
                
                if (Keyboard.isKeyDown(Keyboard.KEY_DOWN)) {
                     viewZ -= 0.5f;
                }           
            while (Keyboard.next()) {
	    if (Keyboard.getEventKeyState()) {
                    if (Keyboard.getEventKey() == Keyboard.KEY_SPACE) {
                        changeTexParameter(value);
                        value++;
                        if (value == 5)
                        {
                            value = 0;
                        }
                    }
                }
            }
                changeTexParameter(value);
                
        }
        
       
       float angle = 0.0f;
        
    void render()
    {   

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        modelViewMatrix.push();
        float[] mCamera = cameraFrame.getCameraMatrix();
        modelViewMatrix.multiply4(mCamera); 
        modelViewMatrix.translate(0, 0, viewZ); 
        TextureReplaceShader.useShader();
        TextureReplaceShader.setUniformMatrix4("mvpMatrix", false, transformPipeline.getModelViewProjectionMatrix()); 
        TextureReplaceShader.setUniform1i("textureUnit0",0);
        glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_FLOOR]);        
        floorBatch.draw(TextureReplaceShader.getAttributeLocations());
        
        glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_CEILING]);
        ceilingBatch.draw(TextureReplaceShader.getAttributeLocations());
        glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_BRICK]);
        
        leftWallBatch.draw(TextureReplaceShader.getAttributeLocations());
        rightWallBatch.draw(TextureReplaceShader.getAttributeLocations());
         
	modelViewMatrix.pop();

	Display.update();  
    }
        
       public void create() throws LWJGLException { 
            //Display    
            Display.setDisplayModeAndFullscreen(new DisplayMode(DISPLAY_WIDTH,DISPLAY_HEIGHT)); 
            
            Display.setTitle("Tunnel");
            Display.create();
            
            //Keyboard         
            Keyboard.create();   
            //Mouse          
            Mouse.setGrabbed(false);
            Mouse.create();  
            //OpenGL      
            initGL();    
            resizeGL();  
        }  
       
        public void resizeGL() {   
            glViewport(0,0,DISPLAY_WIDTH ,DISPLAY_HEIGHT);
            frustrum.setPerspective(70f, Display.getWidth()/ Display.getHeight(),1.0f, 100.0f);
            projectionMatrix = new MatrixStack(frustrum.getProjectionMatrix());
            // Set the transformation pipeline to use the two matrix stacks 
            transformPipeline = new GeometryTransform(modelViewMatrix, projectionMatrix);
        } 
       
            public void destroy() {  
                //Methods already check if created before destroying.  
                Mouse.destroy();   
                Keyboard.destroy();
                Display.destroy();     
            } 
        
            public static void main(String[] args) {
            Tunnel main = null;
            try { 
                 main = new Tunnel(); 
                main.create(); 
                main.run();
            }          
            catch(Exception ex) {  
                 
            }          
            finally { 
                if(main != null) {  
                    main.destroy();
                }         
            }      
        } 
            
            public void run() 
            {
                while (!Display.isCloseRequested() && !Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {              
                
                pollInput();
                render();
       
             
                Display.sync(60);
                Display.setVSyncEnabled(true); 
                
                }
            }
                        
                            	/** 
	 * Calculate how many milliseconds have passed 
	 * since last frame.
	 * 
	 * @return milliseconds passed since last frame 
	 */
	public int getDelta() {
	    long time = getTime();
	    int delta = (int) (time - lastFrame);
	    lastFrame = time;
	 
	    return delta;
	}
	
	/**
	 * Get the accurate system time
	 * 
	 * @return The system time in milliseconds
	 */
	public long getTime() {
	    return (Sys.getTime() * 1000) / Sys.getTimerResolution();
	}
        
        private int loadTexture(String filename, int minFilter, int magFilter, int wrapMode){
            
            InputStream in = null;
            int texID = glGenTextures();
            try {
                in = new FileInputStream("textures/" + filename + ".png");
                PNGDecoder decoder = new PNGDecoder(in);
                int width = decoder.getWidth();
                int height = decoder.getHeight();
                
                ByteBuffer buffer = BufferUtils.createByteBuffer(4 * width * height);
                decoder.decode(buffer, width * 4, PNGDecoder.Format.RGBA);          
                buffer.flip();
                in.close();
                
                glBindTexture(GL_TEXTURE_2D, texID);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
                
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
                
                glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
                glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

//                             
                if(minFilter == GL_LINEAR_MIPMAP_LINEAR || 
                    minFilter == GL_LINEAR_MIPMAP_NEAREST ||
                     minFilter == GL_NEAREST_MIPMAP_LINEAR ||
                     minFilter == GL_NEAREST_MIPMAP_NEAREST){
                glGenerateMipmap(GL_TEXTURE_2D);}       
                glBindTexture(GL_TEXTURE_2D, 0);
                
            } catch (FileNotFoundException e) {
                System.err.println("Failed to find the texture file: " + filename + ".png");
                
            } catch (IOException ex) {
                System.err.println("Error reading file: " + filename + ".png");
                
            } 
           
            return texID;
        }
        
}
